/*******************************************************************************
 * Copyright (c) 2009 Markus Knittig and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Markus Knittig - initial API and implementation
 *******************************************************************************/
package com.googlecode.mylyn.core.client;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.mylyn.commons.net.AuthenticationCredentials;
import org.eclipse.mylyn.commons.net.AuthenticationType;
import org.eclipse.mylyn.tasks.core.TaskRepository;

import com.google.gdata.client.projecthosting.IssuesQuery;
import com.google.gdata.client.projecthosting.ProjectHostingService;
import com.google.gdata.data.IEntry;
import com.google.gdata.data.IFeed;
import com.google.gdata.data.Person;
import com.google.gdata.data.projecthosting.IssueCommentsEntry;
import com.google.gdata.data.projecthosting.IssueCommentsFeed;
import com.google.gdata.data.projecthosting.IssuesEntry;
import com.google.gdata.data.projecthosting.IssuesFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;
import com.googlecode.mylyn.core.GoogleCodeCorePlugin;
import com.googlecode.mylyn.core.util.GoogleCodeUtils;

/**
 * Default implementation of {@IGoogleCodeClient} that wraps the GData API
 * using {@link ProjectHostingService}.
 */
class GDataGoogleCodeClient implements IGoogleCodeClient {

    private final ProjectHostingService projectHostingService;

    private final TaskRepository repository;

    private final String userName;

    /**
     * Initializes a new {@link GoogleCodeClient}. Attention this constructor
     * may do IO.
     * 
     * @category IO
     * @param taskRepository the task repository
     * @throws CoreException if the login fails
     */
    GDataGoogleCodeClient(TaskRepository taskRepository) throws CoreException {
        this.projectHostingService = new ProjectHostingService("googlecode-mylyn-connector");
        AuthenticationCredentials credentials = taskRepository.getCredentials(AuthenticationType.REPOSITORY);
        if (credentials != null) {
            //not anonymous
            try {
                this.projectHostingService.setUserCredentials(credentials.getUserName(),
                        credentials.getPassword());
            } catch (AuthenticationException e) {
                Status status = new Status(IStatus.ERROR, GoogleCodeCorePlugin.PLUGIN_ID, "invalid credentials", e);
                throw new CoreException(status);
            }
        }
        this.repository = taskRepository;
        this.userName = taskRepository.getUserName();
    }

    public IssuesFeed getAllIssues(IProgressMonitor mointor) throws CoreException {
        String feedUrl = getIssuesUrl();
        return this.getFeed(feedUrl, IssuesFeed.class);
    }

    private String getIssuesUrl() {
        return GoogleCodeUtils.getRepositoryFeedUrl(repository.getRepositoryUrl()) + "/issues/full";
    }

    public IssuesEntry getEntry(String issueId, IProgressMonitor monitor) throws CoreException {
        try {
            String repositoryUrl = repository.getRepositoryUrl();
            URL feedUrl = new URL(GoogleCodeUtils.getRepositoryFeedUrl(repositoryUrl) + "/issues/full/" + issueId);
            return this.projectHostingService.getEntry(feedUrl, IssuesEntry.class);
        } catch (MalformedURLException e) {
            Status status = new Status(IStatus.ERROR, GoogleCodeCorePlugin.PLUGIN_ID, "invalid project URL", e);
            throw new CoreException(status);
        } catch (IOException e) {
            Status status = new Status(IStatus.ERROR, GoogleCodeCorePlugin.PLUGIN_ID, "error sending request or reading the feed", e);
            throw new CoreException(status);
        } catch (ServiceException e) {
            Status status = new Status(IStatus.ERROR, GoogleCodeCorePlugin.PLUGIN_ID, "system error retrieving feed", e);
            throw new CoreException(status);
        }
    }

    public IssueCommentsFeed getAllComments(String issueId, IProgressMonitor monitor)
            throws CoreException {
        String feedUrl = getCommentsUrl(issueId);
        return getFeed(feedUrl, IssueCommentsFeed.class);
    }

    private String getCommentsUrl(String issueId) {
        String repositoryUrl = repository.getRepositoryUrl();
        return GoogleCodeUtils.getRepositoryFeedUrl(repositoryUrl) + "/issues/" + issueId + "/comments/full";
    }

    private <F extends IFeed> F getFeed(String feedUrl, Class<F> feedClass) throws CoreException {
        try {
            return this.projectHostingService.getFeed(new URL(feedUrl), feedClass);
        } catch (MalformedURLException e) {
            Status status = new Status(IStatus.ERROR, GoogleCodeCorePlugin.PLUGIN_ID, "invalid project URL", e);
            throw new CoreException(status);
        } catch (IOException e) {
            Status status = new Status(IStatus.ERROR, GoogleCodeCorePlugin.PLUGIN_ID, "error sending request or reading the feed", e);
            throw new CoreException(status);
        } catch (ServiceException e) {
            Status status = new Status(IStatus.ERROR, GoogleCodeCorePlugin.PLUGIN_ID, "system error retrieving feed", e);
            throw new CoreException(status);
        }
    }
    
    private <F extends IFeed> F queryFeed(IssuesQuery query, Class<F> feedClass) throws CoreException {
        try {
            return this.projectHostingService.query(query, feedClass);
        } catch (MalformedURLException e) {
            Status status = new Status(IStatus.ERROR, GoogleCodeCorePlugin.PLUGIN_ID, "invalid project URL", e);
            throw new CoreException(status);
        } catch (IOException e) {
            Status status = new Status(IStatus.ERROR, GoogleCodeCorePlugin.PLUGIN_ID, "error sending request or reading the feed", e);
            throw new CoreException(status);
        } catch (ServiceException e) {
            Status status = new Status(IStatus.ERROR, GoogleCodeCorePlugin.PLUGIN_ID, "system error retrieving feed", e);
            throw new CoreException(status);
        }
    }

    private <E extends IEntry> E insert(String feedUrl, E entry) throws CoreException {
        try {
            return this.projectHostingService.insert(new URL(feedUrl), entry);
        } catch (MalformedURLException e) {
            Status status = new Status(IStatus.ERROR, GoogleCodeCorePlugin.PLUGIN_ID, "invalid project URL", e);
            throw new CoreException(status);
        } catch (IOException e) {
            Status status = new Status(IStatus.ERROR, GoogleCodeCorePlugin.PLUGIN_ID, "error sending request or reading the feed", e);
            throw new CoreException(status);
        } catch (ServiceException e) {
            Status status = new Status(IStatus.ERROR, GoogleCodeCorePlugin.PLUGIN_ID, "system error retrieving feed", e);
            throw new CoreException(status);
        }
    }

    public IssuesEntry createIssue(IssuesEntry entry, IProgressMonitor monitor) throws CoreException {

        return insert(getIssuesUrl(), entry);
    }

    public Person getCurrentUser() {
        Person person = new Person();
        person.setName(this.userName);
        return person;
    }

    public IssueCommentsEntry updateIssue(String issueId, IssueCommentsEntry entry, IProgressMonitor monitor)
            throws CoreException {
        
        return insert(getCommentsUrl(issueId), entry);
    }

    public IssuesFeed getQueryIssues(IssuesQuery query, IProgressMonitor monitor)
            throws CoreException {
        return queryFeed(query, IssuesFeed.class);
    }

    public IssuesQuery createNewIssuesQuery() {
        String issuesUrl = this.getIssuesUrl();
        try {
            return new IssuesQuery(new URL(issuesUrl));
        } catch (MalformedURLException e) {
            throw new RuntimeException("invalid url:" + issuesUrl, e);
        }
    }
    
    public String createIssueId(String issueId) {
        return getIssuesUrl() + '/' + issueId;
    }

}
